/****************************************************************************
 *
 * Copyright (C) 2003-2008 Los Alamos National Security, LLC
 *                         Packet Analytics Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License Version 2 as
 * published by the Free Software Foundation.  You may not use, modify or
 * distribute this program under any other version of the GNU General
 * Public License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 ****************************************************************************/
package nfse;

import java.util.*;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.Socket;
import java.net.URL;
import java.sql.*;

import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;

/**
 * This class handles the migration of dynamic database tables to static b+
 * trees. The database server needs to be well-tuned for this code to run
 * effectively.
 * 
 * @author bduphoff
 * 
 */
public class AutoMigrate extends Thread {

	/***************************************************************************
	 * Used to determine when to migrate data older than n days.
	 */
	private int numDays = 30;

	/***************************************************************************
	 * The minimum number of dynamic tables to keep, even if the data is very
	 * old. These tables perform better than b+ trees but require more overhead.
	 * Consider the cost-benefit trade-off when altering this value.
	 */
	private int minDynamic = 3;

	private int maxDynamic = 1000;

	public AutoMigrate(int numDays, int minDynamic, int maxDynamic) {
		super();
		this.numDays = numDays;
		this.minDynamic = minDynamic;
		this.maxDynamic = maxDynamic;
	}

	/***************************************************************************
	 * Thread runs forever, continuously checking to see if dynamic tables need
	 * to be converted to b+ trees.
	 */
	public void run() {
		try {
			while (true) {

				boolean migrated = false;

				SensorInfo[] sensors = NetFSE.getSensorInfo();
				int[] types = NetFSE.getTypes();

				/***************************************************************
				 * For each type in the system, find sensors that are ready to
				 * migrate. When a sensor is found to be in need of migration,
				 * send a request to the DataServer to initiate the migration.
				 */
				for (int i = 0; i < types.length; i++) {
					String typeName = NetFSE.getTypeName(types[i]);
					String typeClassName = NetFSE.getTypeClassName(types[i]);
					NetFSERecord rec = (NetFSERecord) NetFSE.TYPE_CLASSES
							.get(new Integer(types[i]));

					/***********************************************************
					 * Check each sensor for this type to see if a migrate is
					 * needed.
					 */
					for (int j = 0; j < sensors.length; j++) {
						if (sensors[j].getToolID() == rec.getTypeID()) {
							try {
								Class.forName("org.postgresql.Driver");
								String url = "jdbc:postgresql://"
										+ NetFSE.METADATA_HOST + "/"
										+ NetFSE.METADATA_DATABASE;
								Properties props = new Properties();
								props.setProperty("user", NetFSE.METADATA_USER);
								props.setProperty("password",
										NetFSE.METADATA_PASSWORD);
								props.setProperty("client_encoding", "UTF8");
								Connection c = DriverManager.getConnection(url,
										props);

								Statement s = c.createStatement();

								/***********************************************
								 * Create a calendar now - numDays to determine
								 * at which point to migrate data.
								 */
								GregorianCalendar cal = new GregorianCalendar();
								cal.setTimeInMillis(System.currentTimeMillis());
								cal.add(Calendar.DAY_OF_MONTH, -1 * numDays);
								Timestamp maxTS = new Timestamp(cal.getTime()
										.getTime());

								boolean needToMigrate = false;
								/********
								 * First check to see if the max number of
								 * dynamic indexes is exceeded.
								 */
								String sql = "select count(*) from Temp_Tables where Sensor_ID="
										+ sensors[j].getSensorID()
										+ " and Node_ID="
										+ NetFSE.NODE_ID
										+ " and max_ts is not null  ";
								ResultSet rs = s.executeQuery(sql);
								if (rs.next() && rs.getLong(1) > maxDynamic) {
									needToMigrate = true;
								} else {
									rs.close();

									// Next, check for old data that needs to be migrated
									sql = "select * from Temp_Tables where Sensor_ID="
											+ sensors[j].getSensorID()
											+ " and Node_ID="
											+ NetFSE.NODE_ID
											+ " and max_ts is not null and max_ts < '"
											+ maxTS
											+ "' order by max_ts desc offset "
											+ minDynamic;
									rs = s.executeQuery(sql);
									long count = 0;
									if (rs.next()) {
										needToMigrate = true;
									}
								}
								rs.close();

								if (needToMigrate) {

									long ts = System.currentTimeMillis();

									System.out.println("Starting migrate for "
											+ typeName + ", sensor "
											+ sensors[j].getSensorID() + ".");
									rs.close();

									// Connect to the DataServer to start a
									// migrate
									SocketFactory sf = null;
									if (NetFSE.USE_SSL_DATA)
										sf = SSLSocketFactory.getDefault();
									else
										sf = SocketFactory.getDefault();
									Socket sock = sf.createSocket("localhost",
											NetFSE.DATA_SERVER_PORT);

									DataInputStream dis = new DataInputStream(
											sock.getInputStream());
									DataOutputStream dos = new DataOutputStream(
											sock.getOutputStream());

									// Set the flags for the migrate
									dos.writeInt(sensors[j].getSensorID());
									dos.writeInt(DataSocket.OP_MIGRATE);
									int val = dis.readInt();
									val = dis.readInt(); // wait for the server

									if (val == 0) {
										// No migration was needed
										try {
											dos.writeInt(1);
											dos.flush();
										} catch (Exception e) {
											e.printStackTrace();
										}
										dos.close();
										dis.close();
										sock.close();
									} else {
										// Migration to finished
										try {
											dos.writeInt(1);
											dos.flush();
										} catch (Exception e) {
											e.printStackTrace();
										}
										dos.close();
										dis.close();
										sock.close();

										System.out
												.println("Migrate time was "
														+ (System
																.currentTimeMillis() - ts)
														/ 1000 + " seconds");
										migrated = true;
									}

								}
								s.close();
								c.close();
							} catch (Exception e) {
								e.printStackTrace();
							}
						}
					}

				}

				// If nothing was done, wait 2 minutes,
				// else keep going and restart the loop
				if (!migrated) {
					// System.out.println("Sleeping...");
					Thread.sleep(2 * 60 * 1000); // sleep for 2 minutes
				}
			}
		} catch (Exception E) {
			E.printStackTrace();
		}
		// For testing
		System.out.println("AutoMigrate thread exited on an error");
	}

}
